/*
	File:	video/Enabling.hpp
	Date:	2013-06-01
	Author:	Asteroth
*/


#ifndef __ZEN_ENABLING_HPP__
#define __ZEN_ENABLING_HPP__

#include "../math.hpp"
#include "../gl.hpp"

namespace zen {

	namespace video {


		class Enabled {

		public:

			static const glm::uint ALPHA_TEST = 0;
			static const glm::uint AUTO_NORMAL = 1;
			static const glm::uint BLEND = 2;
			static const glm::uint CLIP_PLANE = 3;
			static const glm::uint COLOR_LOGIC_OPERATION = 4;
			static const glm::uint COLOR_MATERIAL = 5;
			static const glm::uint CULL_FACE = 6;
			static const glm::uint DEPTH_TEST = 7;
			static const glm::uint DITHER = 8;
			static const glm::uint FOG = 9;
			static const glm::uint INDEX_LOGIC_OPERATION = 10;
			static const glm::uint LINE_STIPPLE = 11;
			static const glm::uint NORMALIZE = 12;
			static const glm::uint POINT_SMOOTH = 13;
			static const glm::uint LINE_SMOOTH = 14;
			static const glm::uint POLYGON_SMOOTH = 15;
			static const glm::uint SCISSOR_TEST = 16;
			static const glm::uint STENCIL_TEST = 17;
			static const glm::uint TEXTURE1D = 18;
			static const glm::uint TEXTURE2D = 19;
			static const glm::uint TEXTURE3D = 20;
			static const glm::uint WIREFRAME = 21;
			static const glm::uint SIZE = 22;

			static inline bool alphaTest()                      { return enabled[ALPHA_TEST]; }
			static inline bool autoNormal()                     { return enabled[AUTO_NORMAL]; }
			static inline bool blend()                          { return enabled[BLEND]; }
			static inline bool clipPlane()						{ return enabled[CLIP_PLANE]; }
			static inline bool colorLogicOperation()            { return enabled[COLOR_LOGIC_OPERATION]; }
			static inline bool colorMaterial()                  { return enabled[COLOR_MATERIAL]; }
			static inline bool cullFace()                       { return enabled[CULL_FACE]; }
			static inline bool depthTest()                      { return enabled[DEPTH_TEST]; }
			static inline bool dither()                         { return enabled[DITHER]; }
			static inline bool fog()                            { return enabled[FOG]; }
			static inline bool indexLogicOperation()            { return enabled[INDEX_LOGIC_OPERATION]; }
			static inline bool lineStipple()                    { return enabled[LINE_STIPPLE]; }
			static inline bool normalize()                      { return enabled[NORMALIZE]; }
			static inline bool pointSmooth()                    { return enabled[POINT_SMOOTH]; }
			static inline bool lineSmooth()                     { return enabled[LINE_SMOOTH]; }
			static inline bool polygonSmooth()                  { return enabled[POLYGON_SMOOTH]; }
			static inline bool scissorTest()                    { return enabled[SCISSOR_TEST]; }
			static inline bool stencilTest()                    { return enabled[STENCIL_TEST]; }
			static inline bool texture1D()                      { return enabled[TEXTURE1D]; }
			static inline bool texture2D()                      { return enabled[TEXTURE2D]; }
			static inline bool texture3D()                      { return enabled[TEXTURE3D]; }
			static inline bool wireframe()                      { return enabled[WIREFRAME]; }


		private:

			friend class Enable;
			friend class Disable;
			static bool enabled[SIZE];
			static bool initialized;

			static bool initialize();

		};


		class Enable {

		public:

			/**
			* Enable alpha test.
			* function: GL_NEVER, GL_LESS, GL_EQUAL, GL_LEQUAL, GL_GREATER, GL_NOTEQUAL, GL_GEQUAL, and GL_ALWAYS
			* reference: the value to compare with
			*/
			static inline void alphaTest(GLenum function = GL_GREATER, GLclampf reference = 0) {
				Enabled::enabled[Enabled::ALPHA_TEST] = true;
				glEnable(GL_ALPHA_TEST);
				glAlphaFunc(function, reference);
			}

			static inline void autoNormal() {
				Enabled::enabled[Enabled::AUTO_NORMAL] = true;
				glEnable(GL_AUTO_NORMAL);
			}

			/**
			* Enable alpha blending.
			* sourceFactor: GL_ZERO, GL_ONE, GL_DST_COLOR, GL_ONE_MINUS_DST_COLOR, GL_SRC_ALPHA,
			*      GL_ONE_MINUS_SRC_ALPHA, GL_DST_ALPHA, GL_ONE_MINUS_DST_ALPHA, and GL_SRC_ALPHA_SATURATE
			* destFactor: GL_ZERO, GL_ONE, GL_SRC_COLOR, GL_ONE_MINUS_SRC_COLOR, GL_SRC_ALPHA,
			*      GL_ONE_MINUS_SRC_ALPHA, GL_DST_ALPHA, and GL_ONE_MINUS_DST_ALPHA
			*/
			static inline void blend(GLenum sourceFactor = GL_SRC_ALPHA, GLenum destFactor = GL_ONE_MINUS_SRC_ALPHA) {
				Enabled::enabled[Enabled::BLEND] = true;
				glEnable(GL_BLEND);
				glBlendFunc(sourceFactor, destFactor);
			}

			/**
			* Enable clipping planes.
			* clipPlane: GL_CLIP_PLANEi(6)
			* equation: GLdouble[4] - equation of the plane
			*/
			static inline void clipPlane(GLenum clipPlane, const GLdouble *equation) {
				Enabled::enabled[Enabled::CLIP_PLANE] = true;
				glEnable(clipPlane);
				glClipPlane(clipPlane, equation);
			}

			/**
			* Enable color operation (default GL_SET).
			* operation: GL_CLEAR, GL_SET, GL_COPY, GL_COPY_INVERTED, GL_NOOP, GL_INVERT, GL_AND,
			*      GL_NAND, GL_OR, GL_NOR, GL_XOR, GL_EQUIV, GL_AND_REVERSE, GL_AND_INVERTED,
			*      GL_OR_REVERSE, and GL_OR_INVERTED
			*/
			static inline void colorLogicOperation(GLenum operation) {
				Enabled::enabled[Enabled::COLOR_LOGIC_OPERATION] = true;
				glEnable(GL_COLOR_LOGIC_OP);
				glLogicOp(operation);
			}

			/**
			* Enable color material (makes the glColor*() function to set material properties).
			* face: GL_FRONT, GL_BACK, and GL_FRONT_AND_BACK
			* mode: GL_EMISSION, GL_AMBIENT, GL_DIFFUSE, GL_SPECULAR, and GL_AMBIENT_AND_DIFFUSE
			*/
			static inline void colorMaterial(GLenum face, GLenum mode) {
				Enabled::enabled[Enabled::COLOR_MATERIAL] = true;
				glColorMaterial(face, mode);
				glEnable(GL_COLOR_MATERIAL);
			}

			/**
			* Enable cull face.
			* face: GL_FRONT, GL_BACK or GL_FRONT_AND_BACK
			*/
			static inline void cullFace(GLenum face = GL_BACK) {
				Enabled::enabled[Enabled::CULL_FACE] = true;
				glEnable(GL_CULL_FACE);
				glCullFace(face);
			}

			/**
			* Enable depth test.
			* function: GL_NEVER, GL_LESS, GL_EQUAL, GL_LEQUAL, GL_GREATER, GL_NOTEQUAL, GL_GEQUAL, and GL_ALWAYS
			* near, far: 0..1
			*/
			static inline void depthTest(GLenum function = GL_LEQUAL, GLclampd nearDist = 0, GLclampd farDist = 1) {
				Enabled::enabled[Enabled::DEPTH_TEST] = true;
				glEnable(GL_DEPTH_TEST);
				glDepthFunc(function);
				glDepthRange(nearDist, farDist);
			}

			/**
			* Enable dithering.
			*/
			static inline void dither() {
				Enabled::enabled[Enabled::DITHER] = true;
				glEnable(GL_DITHER);
			}

			/**
			* Enable fog.
			* mode: GL_LINEAR, GL_EXP, and GL_EXP2
			*/
			static inline void fog(GLenum mode = GL_EXP, GLfloat density = 0.35f, GLfloat start = 1, GLfloat end = 5) {
				Enabled::enabled[Enabled::FOG] = true;
				glFogi(GL_FOG_MODE, mode);
				glFogf(GL_FOG_DENSITY, density);
				glFogf(GL_FOG_START, start);
				glFogf(GL_FOG_END, end);
				glEnable(GL_FOG);
			}

			/**
			* Enable index logic operation (a pre historic functionality?).
			* operation: GL_CLEAR, GL_SET, GL_COPY, GL_COPY_INVERTED, GL_NOOP, GL_INVERT, GL_AND,
			*      GL_NAND, GL_OR, GL_NOR, GL_XOR, GL_EQUIV, GL_AND_REVERSE, GL_AND_INVERTED,
			*      GL_OR_REVERSE, and GL_OR_INVERTED
			*/
			static inline void indexLogicOperation(GLenum operation) {
				Enabled::enabled[Enabled::INDEX_LOGIC_OPERATION] = true;
				glEnable(GL_INDEX_LOGIC_OP);
				glLogicOp(operation);
			}

			/**
			* Enable line stipple (example pattern: 1110111010011001, resulting drawed line pattern: --- --- -  --  -).
			*/
			static inline void lineStipple(GLint factor, GLushort pattern) {
				Enabled::enabled[Enabled::LINE_STIPPLE] = true;
				glEnable(GL_LINE_STIPPLE);
				glLineStipple(factor, pattern);
			}
			
			/**
			* Enable automatic normalization of normals set with glNormal*().
			*/
			static inline void normalize() {
				Enabled::enabled[Enabled::NORMALIZE] = true;
				glEnable(GL_NORMALIZE);
			}
			
			/**
			* Enable antialiasing with points.
			*/
			static inline void pointSmooth() {
				Enabled::enabled[Enabled::POINT_SMOOTH] = true;
				glEnable(GL_POINT_SMOOTH);
			}
			
			/**
			* Enable antialiasing with lines.
			*/
			static inline void lineSmooth() {
				Enabled::enabled[Enabled::LINE_SMOOTH] = true;
				glEnable(GL_LINE_SMOOTH);
			}
			
			/**
			* Enable antialiasing with polygons.
			*/
			static inline void polygonSmooth() {
				Enabled::enabled[Enabled::POLYGON_SMOOTH] = true;
				glEnable(GL_POLYGON_SMOOTH);
			}
			
			/**
			* Enable scissor test.
			*/
			static inline void scissorTest(GLint x, GLint y, GLsizei width, GLsizei height) {
				Enabled::enabled[Enabled::SCISSOR_TEST] = true;
				glEnable(GL_SCISSOR_TEST);
				glScissor(x, y, width, height);
			}
			
			/**
			* Enable stencil test.
			*/
			static inline void stencilTest(GLenum function = GL_ALWAYS, GLint reference = 0, GLuint mask = 1,
					GLenum failOp = GL_KEEP, GLenum zfailOp = GL_KEEP, GLenum zpassOp = GL_KEEP) {
				Enabled::enabled[Enabled::STENCIL_TEST] = true;
				glEnable(GL_STENCIL_TEST);
				glStencilFunc(function, reference, mask);
				glStencilOp(failOp, zfailOp, zpassOp);
			}
			
			/**
			* Enable texturing.
			*/
			static inline void texture1D() {
				Enabled::enabled[Enabled::TEXTURE1D] = true;
				glEnable(GL_TEXTURE_1D);
			}

			static inline void texture2D() {
				Enabled::enabled[Enabled::TEXTURE2D] = true;
				glEnable(GL_TEXTURE_2D);
			}

			static inline void texture3D() {
				Enabled::enabled[Enabled::TEXTURE3D] = true;
				glEnable(GL_TEXTURE_3D_EXT);
			}
			
			/**
			* Enable simulated wiregrame.
			*/
			static inline void wireframe() {
				Enabled::enabled[Enabled::WIREFRAME] = true;
				glPolygonMode(GL_FRONT, GL_LINE);
			}

		};


		class Disable {

		public:

			static inline void alphaTest()                      { Enabled::enabled[Enabled::ALPHA_TEST] = false; glDisable(GL_ALPHA_TEST); }
			static inline void autoNormal()                     { Enabled::enabled[Enabled::AUTO_NORMAL] = false; glDisable(GL_AUTO_NORMAL); }
			static inline void blend()                          { Enabled::enabled[Enabled::BLEND] = false; glDisable(GL_BLEND); }
			static inline void clipPlane(GLenum clipPlane)      { Enabled::enabled[Enabled::CLIP_PLANE] = false; glDisable(clipPlane); }
			static inline void colorLogicOperation()            { Enabled::enabled[Enabled::COLOR_LOGIC_OPERATION] = false; glDisable(GL_COLOR_LOGIC_OP); }
			static inline void colorMaterial()                  { Enabled::enabled[Enabled::COLOR_MATERIAL] = false; glDisable(GL_COLOR_MATERIAL); }
			static inline void cullFace()                       { Enabled::enabled[Enabled::CULL_FACE] = false; glDisable(GL_CULL_FACE); }
			static inline void depthTest()                      { Enabled::enabled[Enabled::DEPTH_TEST] = false; glDisable(GL_DEPTH_TEST); }
			static inline void dither()                         { Enabled::enabled[Enabled::DITHER] = false; glDisable(GL_DITHER); }
			static inline void fog()                            { Enabled::enabled[Enabled::FOG] = false; glDisable(GL_FOG); }
			static inline void indexLogicOperation()            { Enabled::enabled[Enabled::INDEX_LOGIC_OPERATION] = false; glDisable(GL_INDEX_LOGIC_OP); }
			static inline void lineStipple()                    { Enabled::enabled[Enabled::LINE_STIPPLE] = false; glDisable(GL_LINE_STIPPLE); }
			static inline void normalize()                      { Enabled::enabled[Enabled::NORMALIZE] = false; glDisable(GL_NORMALIZE); }
			static inline void pointSmooth()                    { Enabled::enabled[Enabled::POINT_SMOOTH] = false; glDisable(GL_POINT_SMOOTH); }
			static inline void lineSmooth()                     { Enabled::enabled[Enabled::LINE_SMOOTH] = false; glDisable(GL_LINE_SMOOTH); }
			static inline void polygonSmooth()                  { Enabled::enabled[Enabled::POLYGON_SMOOTH] = false; glDisable(GL_POLYGON_SMOOTH); }
			static inline void scissorTest()                    { Enabled::enabled[Enabled::SCISSOR_TEST] = false; glDisable(GL_SCISSOR_TEST); }
			static inline void stencilTest()                    { Enabled::enabled[Enabled::STENCIL_TEST] = false; glDisable(GL_STENCIL_TEST); }
			static inline void texture1D()                      { Enabled::enabled[Enabled::TEXTURE1D] = false; glDisable(GL_TEXTURE_1D); }
			static inline void texture2D()                      { Enabled::enabled[Enabled::TEXTURE2D] = false; glDisable(GL_TEXTURE_2D); }
			static inline void texture3D()                      { Enabled::enabled[Enabled::TEXTURE3D] = false; glDisable(GL_TEXTURE_3D_EXT); }
			static inline void wireframe()                      { Enabled::enabled[Enabled::WIREFRAME] = false; glPolygonMode(GL_FRONT, GL_FILL); }

		};


	} // namespace video

} // namespace zen

#endif // __ZEN_ENABLING_HPP__
